/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.apps.dashclock.configuration; import com.google.android.apps.dashclock.ExtensionHost; import com.google.android.apps.dashclock.ExtensionManager; import com.google.android.apps.dashclock.Utils; import com.google.android.apps.dashclock.ui.SwipeDismissListViewTouchListener; import com.mobeta.android.dslv.DragSortController; import com.mobeta.android.dslv.DragSortListView; import net.nurik.roman.dashclock.R; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; import android.text.Html; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.android.apps.dashclock.ExtensionManager.ExtensionListing; /** * Fragment for allowing the user to configure active extensions, shown within a {@link * ConfigurationActivity}. */ public class ConfigureExtensionsFragment extends Fragment implements ExtensionManager.OnChangeListener, AdapterView.OnItemClickListener { private static final String SAVE_KEY_SELECTED_EXTENSIONS = "selected_extensions"; private ExtensionManager mExtensionManager; private List<ComponentName> mSelectedExtensions = new ArrayList<ComponentName>(); private ExtensionListAdapter mSelectedExtensionsAdapter; private Map<ComponentName, ExtensionManager.ExtensionListing> mExtensionListings = new HashMap<ComponentName, ExtensionManager.ExtensionListing>(); private List<ComponentName> mAvailableExtensions = new ArrayList<ComponentName>(); private PopupMenu mAddExtensionPopupMenu; private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { repopulateAvailableExtensions(); } }; private DragSortListView mListView; public ConfigureExtensionsFragment() { } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set up helper components mExtensionManager = ExtensionManager.getInstance(getActivity()); mExtensionManager.addOnChangeListener(this); if (savedInstanceState == null) { mSelectedExtensions = mExtensionManager.getActiveExtensionNames(); } else { List<String> selected = savedInstanceState .getStringArrayList(SAVE_KEY_SELECTED_EXTENSIONS); for (String s : selected) { mSelectedExtensions.add(ComponentName.unflattenFromString(s)); } } mSelectedExtensionsAdapter = new ExtensionListAdapter(); repopulateAvailableExtensions(); IntentFilter packageChangeIntentFilter = new IntentFilter(); packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); packageChangeIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageChangeIntentFilter.addDataScheme("package"); getActivity().registerReceiver(mPackageChangedReceiver, packageChangeIntentFilter); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ViewGroup rootView = (ViewGroup) inflater.inflate( R.layout.fragment_configure_extensions, container, false); mListView = (DragSortListView) rootView.findViewById(android.R.id.list); mListView.setAdapter(mSelectedExtensionsAdapter); mListView.setEmptyView(rootView.findViewById(android.R.id.empty)); final DragSortController dragSortController = new ConfigurationDragSortController(); mListView.setFloatViewManager(dragSortController); mListView.setDropListener(new DragSortListView.DropListener() { @Override public void drop(int from, int to) { ComponentName name = mSelectedExtensions.remove(from); mSelectedExtensions.add(to, name); mSelectedExtensionsAdapter.notifyDataSetChanged(); } }); final SwipeDismissListViewTouchListener swipeDismissTouchListener = new SwipeDismissListViewTouchListener( mListView, new SwipeDismissListViewTouchListener.DismissCallbacks() { public boolean canDismiss(int position) { return position < mSelectedExtensionsAdapter.getCount() - 1; } public void onDismiss(ListView listView, int[] reverseSortedPositions) { for (int position : reverseSortedPositions) { mSelectedExtensions.remove(position); } repopulateAvailableExtensions(); mSelectedExtensionsAdapter.notifyDataSetChanged(); } }); mListView.setOnItemClickListener(this); mListView.setOnScrollListener(swipeDismissTouchListener.makeScrollListener()); mListView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { return dragSortController.onTouch(view, motionEvent) || (!dragSortController.isDragging() && swipeDismissTouchListener.onTouch(view, motionEvent)); } }); mListView.setItemsCanFocus(true); rootView.findViewById(R.id.empty_add_extension_button).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View view) { showAddExtensionMenu(view); } }); return rootView; } @Override public void onPause() { super.onPause(); mExtensionManager.setActiveExtensions(mSelectedExtensions); } @Override public void onDestroy() { super.onDestroy(); getActivity().unregisterReceiver(mPackageChangedReceiver); mExtensionManager.removeOnChangeListener(this); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); ArrayList<String> selectedExtensions = new ArrayList<String>(); for (ComponentName cn : mSelectedExtensions) { selectedExtensions.add(cn.flattenToString()); } outState.putStringArrayList(SAVE_KEY_SELECTED_EXTENSIONS, selectedExtensions); } private void repopulateAvailableExtensions() { Set<ComponentName> selectedExtensions = new HashSet<ComponentName>(); selectedExtensions.addAll(mSelectedExtensions); boolean selectedExtensionsDirty = false; for (ExtensionListing listing : mExtensionListings.values()) { if (listing.icon != null) { //listing.icon.recycle(); // TODO: recycling causes crashes with the ListView :-( listing.icon = null; } } mExtensionListings.clear(); mAvailableExtensions.clear(); Resources res = getResources(); for (ExtensionListing listing : mExtensionManager.getAvailableExtensions()) { mExtensionListings.put(listing.componentName, listing); if (listing.icon != null) { Bitmap icon = Utils.flattenExtensionIcon(listing.icon, res.getColor(R.color.extension_list_item_color)); listing.icon = (icon != null) ? new BitmapDrawable(res, icon) : null; } if (selectedExtensions.contains(listing.componentName)) { if (!ExtensionHost.supportsProtocolVersion(listing.protocolVersion)) { // If the extension is selected and its protocol isn't supported, // then make it available but remove it from the selection. mSelectedExtensions.remove(listing.componentName); selectedExtensionsDirty = true; } else { // If it's selected and supported, don't add it to the list of available // extensions. continue; } } mAvailableExtensions.add(listing.componentName); } Collections.sort(mAvailableExtensions, new Comparator<ComponentName>() { @Override public int compare(ComponentName cn1, ComponentName cn2) { ExtensionListing listing1 = mExtensionListings.get(cn1); ExtensionListing listing2 = mExtensionListings.get(cn2); return listing1.title.compareToIgnoreCase(listing2.title); } }); if (selectedExtensionsDirty && mSelectedExtensionsAdapter != null) { mSelectedExtensionsAdapter.notifyDataSetChanged(); } if (mAddExtensionPopupMenu != null) { mAddExtensionPopupMenu.dismiss(); mAddExtensionPopupMenu = null; } } @Override public void onItemClick(AdapterView<?> listView, View view, int position, long id) { if (id == -1) { showAddExtensionMenu(view.findViewById(R.id.add_extension_label)); } } private void showAddExtensionMenu(View anchorView) { if (mAddExtensionPopupMenu != null) { mAddExtensionPopupMenu.dismiss(); } mAddExtensionPopupMenu = new PopupMenu(getActivity(), anchorView); for (int i = 0; i < mAvailableExtensions.size(); i++) { ComponentName cn = mAvailableExtensions.get(i); ExtensionListing listing = mExtensionListings.get(cn); String label = (listing == null) ? null : listing.title; if (TextUtils.isEmpty(label)) { label = cn.flattenToShortString(); } if (listing != null && !ExtensionHost.supportsProtocolVersion(listing.protocolVersion)) { label = getString(R.string.incompatible_extension_menu_template, label); } mAddExtensionPopupMenu.getMenu().add(Menu.NONE, i, Menu.NONE, label); } mAddExtensionPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem menuItem) { mAddExtensionPopupMenu.dismiss(); mAddExtensionPopupMenu = null; ComponentName cn = mAvailableExtensions.get(menuItem.getItemId()); ExtensionListing extensionListing = mExtensionListings.get(cn); if (extensionListing == null || !ExtensionHost.supportsProtocolVersion( extensionListing.protocolVersion)) { String title = cn.getShortClassName(); if (extensionListing != null) { title = extensionListing.title; } CantAddExtensionDialog .createInstance(cn.getPackageName(), title) .show(getFragmentManager(), "cantaddextension"); return true; } // Item id == position for this popup menu. mSelectedExtensions.add(cn); repopulateAvailableExtensions(); mSelectedExtensionsAdapter.notifyDataSetChanged(); mListView.smoothScrollToPosition(mSelectedExtensionsAdapter.getCount() - 1); return true; } }); mAddExtensionPopupMenu.show(); } private class ConfigurationDragSortController extends DragSortController { private int mPos; public ConfigurationDragSortController() { super(ConfigureExtensionsFragment.this.mListView, R.id.drag_handle, DragSortController.ON_DOWN, 0); setRemoveEnabled(false); } @Override public int startDragPosition(MotionEvent ev) { int res = super.dragHandleHitPosition(ev); if (res >= mSelectedExtensionsAdapter.getCount() - 1) { return DragSortController.MISS; } return res; } @Override public View onCreateFloatView(int position) { mPos = position; return mSelectedExtensionsAdapter.getView(position, null, mListView); } private int origHeight = -1; @Override public void onDragFloatView(View floatView, Point floatPoint, Point touchPoint) { final int addPos = mSelectedExtensionsAdapter.getCount() - 1; final int first = mListView.getFirstVisiblePosition(); final int lvDivHeight = mListView.getDividerHeight(); if (origHeight == -1) { origHeight = floatView.getHeight(); } View div = mListView.getChildAt(addPos - first); if (touchPoint.x > mListView.getWidth() / 2) { float scale = touchPoint.x - mListView.getWidth() / 2; scale /= (float) (mListView.getWidth() / 5); ViewGroup.LayoutParams lp = floatView.getLayoutParams(); lp.height = Math.max(origHeight, (int) (scale * origHeight)); //Log.d("mobeta", "setting height " + lp.height); floatView.setLayoutParams(lp); } if (div != null) { if (mPos > addPos) { // don't allow floating View to go above // section divider final int limit = div.getBottom() + lvDivHeight; if (floatPoint.y < limit) { floatPoint.y = limit; } } else { // don't allow floating View to go below // section divider final int limit = div.getTop() - lvDivHeight - floatView.getHeight(); if (floatPoint.y > limit) { floatPoint.y = limit; } } } } @Override public void onDestroyFloatView(View floatView) { //do nothing; block super from crashing } } @Override public void onExtensionsChanged() { repopulateAvailableExtensions(); } public class ExtensionListAdapter extends BaseAdapter { private static final int VIEW_TYPE_ITEM = 0; private static final int VIEW_TYPE_ADD = 1; @Override public int getViewTypeCount() { return 2; } @Override public boolean hasStableIds() { return true; } @Override public int getCount() { int numItems = mSelectedExtensions.size(); // Hide add row to show empty view if there are no items. return (numItems == 0) ? 0 : (numItems + 1); } @Override public int getItemViewType(int position) { return (position == getCount() - 1) ? VIEW_TYPE_ADD : VIEW_TYPE_ITEM; } @Override public Object getItem(int position) { return (getItemViewType(position) == VIEW_TYPE_ADD) ? null : mSelectedExtensions.get(position); } @Override public long getItemId(int position) { return (getItemViewType(position) == VIEW_TYPE_ADD) ? -1 : mSelectedExtensions.get(position).hashCode(); } @Override public boolean isEnabled(int position) { return (getItemViewType(position) == VIEW_TYPE_ADD) && mAvailableExtensions.size() > 0; } @Override public boolean areAllItemsEnabled() { return false; } @Override public View getView(int position, View convertView, ViewGroup parent) { switch (getItemViewType(position)) { case VIEW_TYPE_ADD: { if (convertView == null) { convertView = getActivity().getLayoutInflater() .inflate(R.layout.list_item_add_extension, parent, false); } convertView.setEnabled(isEnabled(position)); return convertView; } case VIEW_TYPE_ITEM: { ComponentName cn = (ComponentName) getItem(position); if (convertView == null) { convertView = getActivity().getLayoutInflater() .inflate(R.layout.list_item_extension, parent, false); } TextView titleView = (TextView) convertView.findViewById(android.R.id.text1); TextView descriptionView = (TextView) convertView .findViewById(android.R.id.text2); ImageView iconView = (ImageView) convertView.findViewById(android.R.id.icon1); View settingsButton = convertView.findViewById(R.id.settings_button); final ExtensionListing listing = mExtensionListings.get(cn); if (listing == null || TextUtils.isEmpty(listing.title)) { iconView.setImageBitmap(null); titleView.setText(cn.flattenToShortString()); descriptionView.setVisibility(View.GONE); settingsButton.setVisibility(View.GONE); } else { iconView.setImageDrawable(listing.icon); titleView.setText(listing.title); descriptionView.setVisibility( TextUtils.isEmpty(listing.description) ? View.GONE : View.VISIBLE); descriptionView.setText(listing.description); settingsButton.setVisibility( listing.settingsActivity == null ? View.GONE : View.VISIBLE); settingsButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { try { startActivity(new Intent().setComponent( listing.settingsActivity)); } catch (ActivityNotFoundException e) { // TODO: show error to user } catch (SecurityException e) { // TODO: show error to user } } }); } return convertView; } } return null; } } public static class CantAddExtensionDialog extends DialogFragment { private static final String ARG_EXTENSION_TITLE = "title"; private static final String ARG_EXTENSION_PACKAGE_NAME = "package_name"; public static CantAddExtensionDialog createInstance(String extensionPackageName, String extensionTitle) { CantAddExtensionDialog fragment = new CantAddExtensionDialog(); Bundle args = new Bundle(); args.putString(ARG_EXTENSION_TITLE, extensionTitle); args.putString(ARG_EXTENSION_PACKAGE_NAME, extensionPackageName); fragment.setArguments(args); return fragment; } public CantAddExtensionDialog() { } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final String extensionTitle = getArguments().getString(ARG_EXTENSION_TITLE); final String extensionPackageName = getArguments().getString(ARG_EXTENSION_PACKAGE_NAME); return new AlertDialog.Builder(getActivity()) .setTitle(R.string.incompatible_extension_title) .setMessage(Html.fromHtml(getString( R.string.incompatible_extension_message_search_play_template, extensionTitle))) .setPositiveButton(R.string.search_play, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { Intent playIntent = new Intent(Intent.ACTION_VIEW); playIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); playIntent.setData(Uri.parse( "https://play.google.com/store/apps/details?id=" + extensionPackageName)); try { startActivity(playIntent); } catch (ActivityNotFoundException ignored) { } dialog.dismiss(); } } ) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.dismiss(); } } ) .create(); } } }